package generator

import (
	"fmt"
	"io"
	"runtime"
	"text/template"
)

// SnippetWriter is an attempt to make the template library usable.
// Methods are chainable, and you don't have to check Error() until you're all
// done.
type SnippetWriter struct {
	w       io.Writer
	context *Context
	// Left & right delimiters. text/template defaults to "{{" and "}}"
	// which is totally unusable for go code based templates.
	left, right string
	funcMap     template.FuncMap
	err         error
}

// NewSnippetWriter is
// w is the destination; left and right are the delimiters; @ and $ are both
// reasonable choices.
//
// c is used to make a function for every naming system, to which you can pass
// a type and get the corresponding name.
func NewSnippetWriter(w io.Writer, c *Context, left, right string) *SnippetWriter {
	sw := &SnippetWriter{
		w:       w,
		context: c,
		left:    left,
		right:   right,
		funcMap: template.FuncMap{},
	}
	for name, namer := range c.Namers {
		sw.funcMap[name] = namer.Name
	}
	return sw
}

// Do parses format and runs args through it. You can have arbitrary logic in
// the format (see the text/template documentation), but consider running many
// short templaces, with ordinary go logic in between--this may be more
// readable. Do is chainable. Any error causes every other call to do to be
// ignored, and the error will be returned by Error(). So you can check it just
// once, at the end of your function.
//
// 'args' can be quite literally anything; read the text/template documentation
// for details. Maps and structs work particularly nicely. Conveniently, the
// types package is designed to have structs that are easily referencable from
// the template language.
//
// Example:
//
// sw := generator.NewSnippetWriter(outBuffer, context, "$", "$")
// sw.Do(`The public type name is: $.type|public$`, map[string]interface{}{"type": t})
// return sw.Error()
//
// Where:
// * "$" starts a template directive
// * "." references the entire thing passed as args
// * "type" therefore sees a map and looks up the key "type"
// * "|" means "pass the thing on the left to the thing on the right"
// * "public" is the name of a naming system, so the SnippetWriter has given
//   the template a function called "public" that takes a *types.Type and
//   returns the naming system's name. E.g., if the type is "string" this might
//   return "String".
// * the second "$" ends the template directive.
//
// The map is actually not necessary. The below does the same thing:
//
// sw.Do(`The public type name is: $.|public$`, t)
//
// You may or may not find it more readable to use the map with a descriptive
// key, but if you want to pass more than one arg, the map or a custom struct
// becomes a requirement. You can do arbitrary logic inside these templates,
// but you should consider doing the logic in go and stitching them together
// for the sake of your readers.
//
// TODO: Change Do() to optionally take a list of pairs of parameters (key, value)
// and have it construct a combined map with that and args.
func (s *SnippetWriter) Do(format string, args interface{}) *SnippetWriter {
	if s.err != nil {
		return s
	}
	// Name the template by source file:line so it can be found when
	// there's an error.
	_, file, line, _ := runtime.Caller(1)
	tmpl, err := template.
		New(fmt.Sprintf("%s:%d", file, line)).
		Delims(s.left, s.right).
		Funcs(s.funcMap).
		Parse(format)
	if err != nil {
		s.err = err
		return s
	}
	err = tmpl.Execute(s.w, args)
	if err != nil {
		s.err = err
	}
	return s
}

// Args exists to make it convenient to construct arguments for
// SnippetWriter.Do.
type Args map[interface{}]interface{}

// With makes a copy of a and adds the given key, value pair.
func (a Args) With(key, value interface{}) Args {
	a2 := Args{key: value}
	for k, v := range a {
		a2[k] = v
	}
	return a2
}

// WithArgs makes a copy of a and adds the given arguments.
func (a Args) WithArgs(rhs Args) Args {
	a2 := Args{}
	for k, v := range rhs {
		a2[k] = v
	}
	for k, v := range a {
		a2[k] = v
	}
	return a2
}

// Out is
func (s *SnippetWriter) Out() io.Writer {
	return s.w
}

// Error returns any encountered error.
func (s *SnippetWriter) Error() error {
	return s.err
}
